#include "iConfig.hpp"

#include <glf/input/inputDevice.h>

#include <glf/app.h>
#include <glf/core/assert.hpp>
#include <glf/core/macros.h>
#include <glf/core/timer.h> 
#include <glf/input/inputEvent.h>

namespace glf {
 
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

DEFINE_ROOT_RTTI(InputDevice)
DEFINE_RTTI(Keyboard)
DEFINE_RTTI(Mouse)
DEFINE_RTTI(Gamepad)
DEFINE_RTTI(Ps3Controller)
DEFINE_RTTI(XboxController)
DEFINE_RTTI(OpenKodeController)
DEFINE_RTTI(WiiExtension)
DEFINE_RTTI(WiiNunchuk)
DEFINE_RTTI(WiiClassicController)
DEFINE_RTTI(WiiRemote)
DEFINE_RTTI(DsController)
DEFINE_RTTI(IPhoneController)
DEFINE_RTTI(OspController)
DEFINE_RTTI(GameCubeController)
DEFINE_RTTI(PlaybookController)
DEFINE_RTTI(SdlController)
DEFINE_RTTI(CTRController)
DEFINE_RTTI(PSP2Controller)
DEFINE_RTTI(DuoController)

//------------------------------------------------------------------------------
// SimpleButton
//------------------------------------------------------------------------------

SimpleButton::SimpleButton() 
	:	mState(0)
	,	mValue(0)
{
}

bool SimpleButton::IsDown() const 
{
	return (mState & IS_DOWN_MASK) != 0;
}

bool SimpleButton::IsUp() const 
{
	return !IsDown();
}

int SimpleButton::GetDownCount() const
{
	return (mState & DOWN_COUNT_MASK);
}

ushort SimpleButton::GetValue() const 
{
	return mValue;
}

void SimpleButton::SetValue(ushort value) 
{
	mValue = value;
}

// Up count logic:
// | Action     | A= DownCount | B= IsDown | C= WasDown | UpCount = A - B + C |
// ----------------------------------------------------------------------------
// | New Frame  |            0 |         0 |          0 |                   0 |
// | Touch Down |            1 |         1 |          0 |                   0 |
// | Touch Up   |            1 |         0 |          0 |                   1 |
// | Touch Down |            2 |         1 |          0 |                   1 |
// | Touch Up   |            2 |         0 |          0 |                   2 |
// | Touch Down |            3 |         1 |          0 |                   2 |
// ----------------------------------------------------------------------------
// | New Frame  |            0 |         1 |          1 |                   0 |
// | Touch Up   |            0 |         0 |          1 |                   1 |
// | Touch Down |            1 |         1 |          1 |                   1 |
// | Touch Up   |            1 |         0 |          1 |                   2 |
// ----------------------------------------------------------------------------
// | New Frame  |            0 |         0 |          0 |                   0 |
// | Touch Down |            1 |         1 |          0 |                   0 |
// | Touch Up   |            1 |         0 |          0 |                   1 |
// ...

int SimpleButton::GetUpCount() const
{
	return GetDownCount() - IsDown() + WasDown();
}

bool SimpleButton::WasPressed() const 
{
	return GetDownCount() && WasUp();
}
	
bool SimpleButton::WasReleased() const 
{
	return GetUpCount() && WasDown();
}
	
void SimpleButton::ResetFrame()
{
	mState = IsDown() ? (IS_DOWN_MASK | WAS_DOWN_MASK) : 0;
}

void SimpleButton::UpdateFrame(bool isPressed) 
{
	if (isPressed != IsDown()) { //new transition
		if (isPressed) {
			mState |= IS_DOWN_MASK;
			mState++;		
		}
		else
			mState &= ~IS_DOWN_MASK;
	}
}

bool SimpleButton::WasDown() const 
{
	return (mState & WAS_DOWN_MASK) > 0;

}

bool SimpleButton::WasUp() const 
{
	return !WasDown();
}

//------------------------------------------------------------------------------
// AnalogicStick
//------------------------------------------------------------------------------

const float AnalogicStick::EPSILON = 0.1f;

AnalogicStick::Direction AnalogicStick::GetGlobalDirection() const 
{
	if(GetX() > EPSILON) {
		if(GetY() > EPSILON) {
			return DIRECTION_UPRIGHT;
		} else if(GetY() < -EPSILON) {
			return DIRECTION_DOWNLEFT;
		} else {
			return DIRECTION_RIGHT;
		}
	} else if(GetX() < -EPSILON) {
		if(GetY() > EPSILON) {
			return DIRECTION_UPLEFT;
		} else if(GetY() < -EPSILON) {
			return DIRECTION_DOWNLEFT;
		} else {
			return DIRECTION_LEFT;
		}
	} else {
		if(GetY() > EPSILON) {
			return DIRECTION_UP;
		} else if(GetY() < -EPSILON) {
			return DIRECTION_DOWN;
		} else {
			return DIRECTION_NONE;
		}
	}
}

void AnalogicStick::RaiseChangedEvent(InputDevice *device, int idx, float x, float y)
{
	GamepadSensorEvent sensorEvt(IET_GAMEPAD_SENSOR_STICK_CHANGED);
	sensorEvt.mInputDevice = device;	
	sensorEvt.mSensorIndex = idx;	
	sensorEvt.mValue = Vec3f(x, y, 0.0f);
	GetAppEventMgr().PostEvent(sensorEvt);
}

void AnalogicStick::UpdateFrame(float newX, float newY) 
{
	mAxisX = newX;
	mAxisY = newY;
}

//------------------------------------------------------------------------------
// TouchPad::Touch
//------------------------------------------------------------------------------


TouchPad::Touch::Touch() :
mTouchId(UNDEFINED_TOUCH_ID)
{
}

Point<short> TouchPad::Touch::GetFirstPoint() const
{
	GLF_ASSERT(mTouchId != UNDEFINED_TOUCH_ID);
	return mFirst;
}

Point<short> TouchPad::Touch::GetLastPoint() const
{
	GLF_ASSERT(mTouchId != UNDEFINED_TOUCH_ID);
	const size_t size = mPoints.size();
	return size ? mPoints[size-1] : mFirst;
}
		
Point<short> TouchPad::Touch::GetPoint(int index) const
{
	GLF_ASSERT(mTouchId != UNDEFINED_TOUCH_ID);
	GLF_ASSERT(index >= 0 && index < (int)mPoints.size() + 1);
	return index == 0 ? mFirst : mPoints[index - 1];
}
		
int TouchPad::Touch::GetPointCount() const
{
	GLF_ASSERT(mTouchId != UNDEFINED_TOUCH_ID);
	return mPoints.size() + 1;
}

uint TouchPad::Touch::GetTouchId() const
{
	return mTouchId;
}

//------------------------------------------------------------------------------
// TouchPad
//------------------------------------------------------------------------------

TouchPad::TouchPad() :
mActiveTouchCount(0)
{
	//mActiveTouchIndexes contains touch indexes for: 
	// - all active touches in the range [0, mActiveTouchCount[
	// - all inactive touches in the range [mActiveTouchCount, NUM_TOUCHES[
	for (int i = 0; i < (int)NUM_TOUCHES; i++)
		mActiveTouchIndexes[i] = i;
}

int TouchPad::GetTouchCount() const
{
	return mActiveTouchCount;
}

const TouchPad::Touch &TouchPad::GetTouchByIndex(int index) const
{
	GLF_ASSERT(index < mActiveTouchCount);
	return mTouches[mActiveTouchIndexes[index]];
}

const TouchPad::Touch *TouchPad::GetTouchByUniqueID(uint touchId) const
{
	//The touch is searched in all touches, active and then inactive list,
	//because when a touch ended it is removed from the active list.
	for (int i = 0; i < (int)NUM_TOUCHES; i++)
		if (mTouches[mActiveTouchIndexes[i]].mTouchId == touchId)
			return &mTouches[ mActiveTouchIndexes[ i ] ];
	return 0;
}

void TouchPad::RaiseTouchEvent(InputDevice *device, 
							   int touchPadIndex, 
							   InputEventType event, 
							   uint touchId, 
							   float rx, float ry, bool convertDevice2Screen)
{	
	TouchPadEvent tpEvt(event);
	tpEvt.mInputDevice = device;	
	tpEvt.mTouchPadIndex = touchPadIndex;	
	tpEvt.mTouchId = touchId;
	if (convertDevice2Screen)
		tpEvt.mPoint = App::GetInstance()->ConvertPosDeviceToScreen(Point<float>(rx, ry));
	else
		tpEvt.mPoint = Point<short>(static_cast<short>(rx), static_cast<short>(ry));
	GetAppEventMgr().PostEvent(tpEvt);
}

bool TouchPad::Update(InputEventType event, uint touchId, short x, short y)
{
	if (event == IET_TOUCHPAD_BEGAN) {
		//Get free touch
		if (mActiveTouchCount < (int)NUM_TOUCHES) {
			Touch &touch = mTouches[mActiveTouchIndexes[mActiveTouchCount]];
			touch.mTouchId = touchId;
			touch.mFirst = Point<short>(x, y);
			touch.mPoints.clear();
			mActiveTouchCount++;
			return true;
		}		
	}
	else {
		//Find the corresponding touch
		for (int i = 0; i < mActiveTouchCount; i++) {
			const int touchIndex = mActiveTouchIndexes[i];
			Touch &touch = mTouches[touchIndex];
			if (touch.mTouchId == touchId) {
				touch.mPoints.push_back(Point<short>(x, y));
				if (event == IET_TOUCHPAD_ENDED) { 					
					//swap
					mActiveTouchIndexes[i] = mActiveTouchIndexes[mActiveTouchCount-1];
					mActiveTouchIndexes[mActiveTouchCount-1] = touchIndex;
					mActiveTouchCount--;
				}
				return true;
			}
		}
	}
	//Bad event: in certain conditions, double-click on screen title, SDL could generate 
	//           single release mouse event which is then used to simulate single 
	//           touchpad end event!
	return false;	
}

//------------------------------------------------------------------------------
// InputDevice
//------------------------------------------------------------------------------

InputDevice::InputDevice() 
	: mConnected(false)
	, mOverridenInputManager(0)

{
}

InputDevice::~InputDevice() 
{
}

void InputDevice::ResetFrame()
{
	//reset all buttons
	for (uint i = 0; i < mBtns.Size(); i++)
		mBtns[i].ResetFrame();
	//reset all cursors
	for (uint i = 0; i < mCursors.Size(); i++)
		mCursors[i].ResetFrame();
}

InputManager* InputDevice::GetInputManager() const
{
	if (mOverridenInputManager)
		return mOverridenInputManager;
	return &GetAppInputMgr();
}

void InputDevice::SetInputManager(InputManager* inputManager)
{
	mOverridenInputManager = inputManager;
}

void InputDevice::SetUpdated()
{
	GetInputManager()->AddUpdatedDevice(this);
}

void InputDevice::UpdateButton(uint idx, bool isDown)
{
	if(idx < mBtns.mNum) 
	{
		mBtns[idx].UpdateFrame(isDown);
		SetUpdated();
	}
}

void InputDevice::UpdateCursor(uint idx, bool isVisible, int x, int y)
{
	mCursors[idx].UpdateFrame(isVisible, static_cast<short>(x), static_cast<short>(y));		
	SetUpdated();
}

//------------------------------------------------------------------------------
// Keyboard
//------------------------------------------------------------------------------

bool Keyboard::PreSendEvent(CoreEvent &event)
{
	switch (event.mType) 
	{
	case IET_KEY_PRESSED:
	case IET_KEY_RELEASED:
		KeyboardEvent &kbEvt = static_cast<KeyboardEvent &>(event);
		//update button state
		UpdateButton(kbEvt.mKey, kbEvt.mType == IET_KEY_PRESSED);
		//update keyboard event modifiers with current state
		kbEvt.mModifiers =
			(IsDown(Keyboard::KEY_LCTRL)	?	MODIFIER_CTRL	: 0) |
			(IsDown(Keyboard::KEY_RCTRL)	?	MODIFIER_CTRL	: 0) |
			(IsDown(Keyboard::KEY_LALT)		?	MODIFIER_ALT	: 0) |
			(IsDown(Keyboard::KEY_RALT)		?	MODIFIER_ALT	: 0) |
			(IsDown(Keyboard::KEY_LSHIFT)	?	MODIFIER_SHIFT	: 0) |
			(IsDown(Keyboard::KEY_RSHIFT)	?	MODIFIER_SHIFT	: 0);
		return true;
	}
	return InputDevice::PreSendEvent(event);
}

void Keyboard::RaiseButtonEvent(int idx, bool isDown)
{
	const KeyCode kc = static_cast<KeyCode>(idx);	
	KeyboardEvent ev(isDown ? IET_KEY_PRESSED : IET_KEY_RELEASED);
	ev.mInputDevice = this;
	ev.mChar = GetChar(kc);
	ev.mKey = kc;	
	//ev->mModifiers is updated when event is dispatched via PreSendEvent
	GetAppEventMgr().PostEvent(ev);
}

//------------------------------------------------------------------------------
// Mouse
//------------------------------------------------------------------------------

bool Mouse::PreSendEvent(CoreEvent &event)
{	
	switch (event.mType) 
	{
	case IET_MOUSE_BTN_DOWN:
	case IET_MOUSE_BTN_UP:	
		{
			MouseEvent &msEvt = static_cast<MouseEvent &>(event);	
			//update button state
			UpdateButton(msEvt.mBtn, event.mType == IET_MOUSE_BTN_DOWN);
			//update mouse event pos with current state
			msEvt.mPos = GetPos();
			return true;
		}
	case IET_MOUSE_MOVED:
		{
			MouseEvent &msEvt = static_cast<MouseEvent &>(event);
			//update cursor state
			UpdateCursor(0, true, msEvt.mPos.x, msEvt.mPos.y);
			return true;
		}
	case IET_MOUSE_WHEEL:	
		{
			MouseEvent &msEvt = static_cast<MouseEvent &>(event);
			//update wheel state
			UpdateWheel(msEvt.mWheel);
			//update mouse event pos with current state via PreSendEvent
			msEvt.mPos = GetPos();
			return true;
		}
	}
	return InputDevice::PreSendEvent(event);
}

void Mouse::RaiseMoveEvent(float x, float y, bool convertDevice2Screen)
{
	MouseEvent ev(IET_MOUSE_MOVED);
	ev.mInputDevice = this;
	ev.mBtn = Mouse::BTN_NONE;
	if (convertDevice2Screen)
		ev.mPos = App::GetInstance()->ConvertPosDeviceToScreen(Point<float>(x, y));
	else
		ev.mPos = Point<short>((short)x, (short)y);
	GetAppEventMgr().PostEvent(ev);
}

void Mouse::RaiseWheelEvent(float wheel)
{
	MouseEvent ev(IET_MOUSE_WHEEL);
	ev.mInputDevice = this;
	ev.mBtn = Mouse::BTN_NONE;
	//ev->mPos is updated when event is dispatched via PreSendEvent
	ev.mWheel = wheel;
	GetAppEventMgr().PostEvent(ev);
}

void Mouse::RaiseButtonEvent(int idx, bool isDown)
{
	const Btn btn = static_cast<Btn>(idx);
	MouseEvent ev(isDown ? IET_MOUSE_BTN_DOWN : IET_MOUSE_BTN_UP);
	ev.mInputDevice = this;
	ev.mBtn = btn;
	//ev->mPos is updated when event is dispatched via PreSendEvent
	GetAppEventMgr().PostEvent(ev);
}

void Mouse::ResetFrame()
{
	InputDevice::ResetFrame();
	//reset wheel
	mWheel = 0.0f;
}

void Mouse::UpdateWheel(float wheel) 
{
	mWheel += wheel;
	SetUpdated();
}

//------------------------------------------------------------------------------
// Gamepad
//------------------------------------------------------------------------------

bool Gamepad::PreSendEvent(CoreEvent &event)
{
	switch (event.mType) 
	{
	case IET_GAMEPAD_BUTTON_PRESSED:
	case IET_GAMEPAD_BUTTON_RELEASED:
		{
			GamepadButtonEvent &padEvt = static_cast<GamepadButtonEvent &>(event);
			//update button state
			UpdateButton(padEvt.mBtnIndex, padEvt.mType == IET_GAMEPAD_BUTTON_PRESSED);
			return true;
		}
	case IET_GAMEPAD_CURSOR_IN:
	case IET_GAMEPAD_CURSOR_MOVED:
	case IET_GAMEPAD_CURSOR_OUT:
		{
			GamepadCursorEvent &cursorEvt = static_cast<GamepadCursorEvent &>(event);
			// Use last known (and valid) cursor pos if it's out
			if (event.mType == IET_GAMEPAD_CURSOR_OUT)
				cursorEvt.mPos = Point<short>(
				mCursors[cursorEvt.mCursorIndex].GetPos().x, 
				mCursors[cursorEvt.mCursorIndex].GetPos().y);
			//update cursor state
			UpdateCursor(cursorEvt.mCursorIndex, event.mType != IET_GAMEPAD_CURSOR_OUT, cursorEvt.mPos.x, cursorEvt.mPos.y);
			return true;
		}
	case IET_GAMEPAD_SENSOR_STICK_CHANGED:
		{	
			GamepadSensorEvent &sensorEvt = static_cast<GamepadSensorEvent &>(event);
			// update analogic stick state
			mAnalogicSticks[sensorEvt.mSensorIndex].UpdateFrame(sensorEvt.mValue.x, sensorEvt.mValue.y);
			return true;
		}
	case IET_GAMEPAD_SENSOR_ACCELEROMETER_CHANGED:
		{
			GamepadSensorEvent &sensorEvt = static_cast<GamepadSensorEvent &>(event);
			// update accelerometer state
			UpdateAcc(sensorEvt.mValue.x, sensorEvt.mValue.y, sensorEvt.mValue.z);
			return true;
		}
	case IET_GAMEPAD_SENSOR_GYROSCOPE_CHANGED:
		{
			GamepadSensorEvent &sensorEvt = static_cast<GamepadSensorEvent &>(event);
			// update gyroscope state
			UpdateGyro(sensorEvt.mValue.x, sensorEvt.mValue.y, sensorEvt.mValue.z);
			return true;
		}			
	case IET_TOUCHPAD_BEGAN:
	case IET_TOUCHPAD_MOVED:
	case IET_TOUCHPAD_ENDED:
		{			
			TouchPadEvent &tpEvt = static_cast<TouchPadEvent &>(event);
			//update touchpad state
			return mTouchPads[tpEvt.mTouchPadIndex].Update((InputEventType)tpEvt.mType, tpEvt.mTouchId, tpEvt.mPoint.x, tpEvt.mPoint.y);
		}
	}
	return true;
}

void Gamepad::RaiseAccelerometerEvent(float x, float y, float z)
{
	GamepadSensorEvent accEvt(IET_GAMEPAD_SENSOR_ACCELEROMETER_CHANGED);
	accEvt.mInputDevice = this;
	accEvt.mValue.x = x;
	accEvt.mValue.y = y;
	accEvt.mValue.z = z;
	GetAppEventMgr().PostEvent(accEvt);
}

void Gamepad::RaiseGyroscopeEvent(float x, float y, float z)
{
	GamepadSensorEvent gyroEvt(IET_GAMEPAD_SENSOR_GYROSCOPE_CHANGED);
	gyroEvt.mInputDevice = this;
	gyroEvt.mValue.x = x;
	gyroEvt.mValue.y = y;
	gyroEvt.mValue.z = z;
	GetAppEventMgr().PostEvent(gyroEvt);
}

void Gamepad::RaiseAnalogicStickEvent(int idx, float x, float y)
{
	if (idx < (int)mAnalogicSticks.Size())
		mAnalogicSticks[idx].RaiseChangedEvent(this, idx, x, y);
}

void Gamepad::RaiseButtonEvent(int idx, bool isDown)
{
	if (idx >= 0 && idx < (int)mBtns.Size()) {
		GamepadButtonEvent ev(isDown ? IET_GAMEPAD_BUTTON_PRESSED : IET_GAMEPAD_BUTTON_RELEASED);
		ev.mInputDevice = this;
		ev.mBtnIndex = idx;
		GetAppEventMgr().PostEvent(ev);
	}
}

void Gamepad::RaiseCursorMoveEvent(int idx, short x, short y)
{
	if (idx >= 0 && idx < (int)mCursors.Size()) {
		GamepadCursorEvent ev(IET_GAMEPAD_CURSOR_MOVED);
		ev.mInputDevice = this;
		ev.mCursorIndex = idx;
		ev.mPos = Point<short>(x, y);
		GetAppEventMgr().PostEvent(ev);
	}
}

void Gamepad::RaiseCursorVisibilityEvent(int idx, bool isVisible, short x, short y)
{
	if (idx >= 0 && idx < (int)mCursors.Size()) {
		GamepadCursorEvent ev(isVisible ? IET_GAMEPAD_CURSOR_IN : IET_GAMEPAD_CURSOR_OUT);
		ev.mInputDevice = this;
		ev.mCursorIndex = idx;
		if (isVisible)
			ev.mPos = Point<short>(x, y);
		//else
		//	ev.mPos is updated with last valid pos when event is dispatched via PreSendEvent
		GetAppEventMgr().PostEvent(ev);
	}
}

void Gamepad::RaiseTouchEvent(int touchPadIndex, 
								  InputEventType event, 
								  uint touchId, 
								  float x, float y, bool convertDevice2Screen)
{
	if(touchPadIndex < (int)mTouchPads.Size())
		mTouchPads[touchPadIndex].RaiseTouchEvent(this, touchPadIndex, event, touchId, x, y, convertDevice2Screen);
}

} //namespace glf

